package com.spotmanger.project.core.geohandler;


import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.RowBounds;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @author lww * @date 2020-08-01 18:35
 * 执行
 * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)请求参数处理
 * ParameterHandler (getParameterObject, setParameters)返回结果集处理
 * ResultSetHandler (handleResultSets, handleOutputParameters)SQL语句构建
 * StatementHandler (prepare, parameterize, batch, update, query)
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisGeometryHandler implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof ParameterHandler) {
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
        }
        StatementHandler statementHandler = (StatementHandler) realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        //SELECT操作
        if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            List<ResultMap> resultMaps = mappedStatement.getResultMaps();
            ResultMap map = resultMaps.get(0);
            Class<?> type = map.getType();
            List<Integer> locations = selectGetLocations(type);
            StringBuilder sb = new StringBuilder();
            sb.append("SELECT ");
            String originalSql = boundSql.getSql();
            String reSql = originalSql.replace("SELECT", "");
            int from = reSql.indexOf("FROM");
            String subSql = reSql.substring(0, from);
            String[] split = subSql.split(",");
            for (int i = 0; i < split.length; i++) {
                if (locations.contains(i + 1)) {
                    sb.append("ST_ASTEXT(").append(split[i]).append(") AS ").append(split[i]).append(",");
                } else {
                    sb.append(split[i]).append(",");
                }
            }
            String substring = sb.substring(0, sb.length() - 1);
            String lastSql = reSql.substring(from);
            metaObject.setValue("delegate.boundSql.sql", substring + " " + lastSql);
            metaObject.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
            metaObject.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
        } else if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
            Object paramObj = boundSql.getParameterObject();
            Field[] fields = paramObj.getClass().getDeclaredFields();
            List<Integer> locations = insertGetLocations(fields, paramObj);
            String originalSql = boundSql.getSql();
            int j = 0;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < originalSql.length(); i++) {
                String subStr = originalSql.substring(i, i + 1);
                if ("?".equalsIgnoreCase(subStr)) {
                    j++;
                    if (locations.contains(j)) {
                        sb.append("ST_GEOMFROMTEXT(" + subStr + ")");
                    } else {
                        sb.append(subStr);
                    }
                } else {
                    sb.append(subStr);
                }
            }
            metaObject.setValue("delegate.boundSql.sql", sb.toString());
        } else if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())) {
            Object paramObj = boundSql.getParameterObject();
            if (paramObj instanceof MapperMethod.ParamMap) {
                Field[] fields = (((MapperMethod.ParamMap<?>) paramObj).get("param1")).getClass().getDeclaredFields();
                List<Integer> locations = updateGetLocations(fields, (((MapperMethod.ParamMap<?>) paramObj).get("param1")));
                if (!locations.isEmpty()) {
                    String originalSql = boundSql.getSql();
                    int whereIndex = originalSql.indexOf("WHERE");
                    String orginalSqlWidthOutWhere = originalSql.substring(whereIndex);
                    String desStr = "?";

                    int cnt = 0;
                    int offset = 0;
                    while ((offset = orginalSqlWidthOutWhere.indexOf(desStr, offset)) != -1) {
                        offset = offset + 1;
                        cnt++;
                    }

                    int j = 0 + cnt;
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < originalSql.length(); i++) {
                        String subStr = originalSql.substring(i, i + 1);
                        if ("?".equalsIgnoreCase(subStr)) {
                            j++;
                            if (locations.contains(j)) {
                                sb.append("ST_GEOMFROMTEXT(" + subStr + ")");
                            } else {
                                sb.append(subStr);
                            }
                        } else {
                            sb.append(subStr);
                        }
                    }
                    metaObject.setValue("delegate.boundSql.sql", sb.toString());
                }

            }

        }
        return invocation.proceed();
    }

    private List<Integer> selectGetLocations(Class<?> type) {
        List<Integer> locations = new ArrayList<>();
        Field[] fields = type.getDeclaredFields();
        int i = 0;
        for (Field field : fields) {
            if (!"serialVersionUID".equalsIgnoreCase(field.getName())) {
                i++;
                if (field.getAnnotation(SGeometry.class) != null) {
                    locations.add(i);
                }
            }
        }
        return locations;
    }

    private List<Integer> insertGetLocations(Field[] declaredFields, Object parameterObject) {
        List<Integer> locations = new ArrayList<>();
        int i = 0;
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            try {
                Object o = declaredField.get(parameterObject);
                if (o != null && !"serialVersionUID".equalsIgnoreCase(declaredField.getName())) {
                    i++;
                    SGeometry annotation = AnnotationUtils.findAnnotation(declaredField, SGeometry.class);
                    if (annotation != null) {
                        locations.add(i);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return locations;
    }


    private List<Integer> updateGetLocations(Field[] declaredFields, Object parameterObject) {
        List<Integer> locations = new ArrayList<>();
        int i = 0;
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            try {
                Object o = declaredField.get(parameterObject);
                if (o != null && !"serialVersionUID".equalsIgnoreCase(declaredField.getName())) {
                    i++;
                    SGeometry annotation = AnnotationUtils.findAnnotation(declaredField, SGeometry.class);
                    if (annotation != null) {
                        locations.add(i);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return locations;
    }

    private Object realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return target;
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
